package com.unitd.base.frame.plugin.db.datasource.aop;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.unitd.base.frame.plugin.db.datasource.annotion.DataSource;
import com.unitd.base.frame.plugin.db.datasource.context.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 动态数据源处理
 *
 * @author hudan
 * @version 1.0
 * @filename MultiSourceExAop.java
 * @copyright www.unitd.com
 * @date 2020-07-08
 */
@Aspect
@Slf4j
@Order(-1)
public class MultiSourceExAop {

    private String[] names;

    public MultiSourceExAop(String... names) {
        this.names = names;
    }

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.unitd.base.frame.plugin.db.datasource.annotion.DataSource)")
    private void cut() {
    }

    /**
     * 环绕通知,织入切点
     *
     * @param joinPoint 定义的切点
     * @return 切点对象
     */
    @Around("cut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature;

        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        } else {
            methodSignature = (MethodSignature) signature;
            Object target = joinPoint.getTarget();
            Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
            DataSource datasource = method.getAnnotation(DataSource.class);
            if (datasource != null) {
                // 参数名
                String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
                List<String> argNameList = Arrays.asList(argNames);
                // 得到调用请求的方法参数值
                Object[] argObjects = joinPoint.getArgs();

                // 得到方法参数名称-参数值的 键值对 对象
                Map<String, Object> paramsMap = Maps.newHashMap();
                // 获得当前方法的传入的参数的名称-值的键值对
                resolveArgs(joinPoint, argNameList, argObjects, paramsMap);

                // 根据当前数据源注解对象,以及方法参数,获取与该注解name属性对应的值
                String datasourceName = getAnnotationNameValue(datasource, paramsMap);

                log.debug("当前的得到的需要设置的数据源名称为:[{}]", datasourceName);
                DataSourceContextHolder.setDataSourceType(datasourceName);
            } else {
                DataSourceContextHolder.setDataSourceType(this.names[0]);
                log.debug("设置数据源为：dataSourceCurrent");
            }

            Object proceed;
            try {
                proceed = joinPoint.proceed();
            } finally {
                log.debug("清空数据源信息!");
                DataSourceContextHolder.clearDataSourceType();
            }

            return proceed;
        }
    }

    /**
     * 获得当前方法的传入的参数的名称-值的键值对
     *
     * @param joinPoint   当前注解的切点对象
     * @param argNameList 方法参数名列表
     * @param argObjects  方法参数值
     * @param paramsMap   组装后的方法参数名-值的减值对对象
     */
    private static void resolveArgs(JoinPoint joinPoint, List<String> argNameList, Object[] argObjects,
                                    Map<String, Object> paramsMap) {

        for (int i = 0; i < argNameList.size(); i++) {
            Class<?> classo = argObjects[i].getClass();

            if (handleClassType(classo.getName())) {
                paramsMap.put(argNameList.get(i), joinPoint.getArgs()[i]);
                continue;
            }

            JSONObject object = (JSONObject) JSONObject.toJSON(joinPoint.getArgs()[i]);
            object.keySet().parallelStream().forEach(p -> {
                paramsMap.put(p, object.get(p));
            });
        }
    }

    /**
     * 根据当前数据源注解对象,以及方法参数,获取与该注解name属性对应的值
     *
     * @param myAnnotation 当前注解对象
     * @param paramsMap    方法参数键值对
     * @return 注解的name属性对应的值
     */
    private static String getAnnotationNameValue(DataSource myAnnotation, Map<String, Object> paramsMap) {
        String description = myAnnotation.name();
        return handleAnnotation(description, paramsMap);
    }

    /**
     * 获得数据源注解中,定义的name属性的名称与调用的方法的参数名对应的值,并根据name和值组成相应的Map对象
     *
     * @param datasourceName 注解的name属性的值
     * @param paramsMap      方法参数
     * @return 注解name与方法参s数对应的键值对
     */
    private static String handleAnnotation(String datasourceName, Map<String, Object> paramsMap) {
        AtomicReference<String> result = new AtomicReference<>(datasourceName);
        if (StringUtils.isBlank(datasourceName)) {
            return result.get();
        }
        paramsMap.entrySet().parallelStream().forEach(p -> {
            if (result.get().contains(p.getKey())) {
                result.set(result.get().replaceAll(p.getKey(), p.getValue() + ""));
            }
        });
        return result.get();
    }


    //
//    public static <T> T get(Class<T> clz, Object o) {
//        if (clz.isInstance(o)) {
//            return clz.cast(o);
//        }
//        return null;
//    }
//
//

    /**
     * 判断传入的参数的数据类型是否是基本数据类型
     *
     * @param name 方法参数名
     * @return 是否是基本数据类型
     */
    private static boolean handleClassType(String name) {
        AtomicBoolean result = new AtomicBoolean(false);
        String[] className = {"String", "Integer", "Long", "int", "float", "double", "char"};
        Arrays.asList(className).forEach(n -> {
            if (name.contains(n)) {
                result.set(name.contains(n));
            }
        });
        return result.get();
    }
}